﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace AliCMS.TemplateEngine
{
    internal enum ExpressionType
    {
        Mix,//混合
        StringConstant,//字符串常量

        NumberConstant,//数字常量
        Variable,//变量
        Member,//成员变量，是以“.”操作符调用的变量

        Function//函数
    }

    internal class Expression
    {
        static Expression()
        {
            _precedence = new Dictionary<string, int>();
            _precedence.Add(".", 8);
            _precedence.Add("!", 7);
            _precedence.Add("*", 6);
            _precedence.Add("/", 6);
            _precedence.Add("%", 6);
            _precedence.Add("+", 5);
            _precedence.Add("-", 5);
            _precedence.Add(">", 4);
            _precedence.Add(">=", 4);
            _precedence.Add("<", 4);
            _precedence.Add("<=", 4);
            _precedence.Add("==", 3);
            _precedence.Add("!=", 3);
            _precedence.Add("&&", 2);
            _precedence.Add("||", 1);
        }

        internal Expression(Object source, string value)
            : this(source, value, null)
        {
        }
        internal Expression(Object source, string value, Expression parent)
        {
            _source = source;
            _value = value;
            _parent = parent;
            _operator = String.Empty;
            _type = ExpressionType.Mix;
            SliceToPart();
        }

        internal string Parse()
        {
            if (_type == ExpressionType.Mix)
            {
                if (_operator != ".")
                    return "(" + _left.Parse() + _operator + _right.Parse() + ")";
                else
                    return _left.Parse() + _operator + _right.Parse();
            }
            else if (_type == ExpressionType.StringConstant)
            {
                if (_value.StartsWith("'") && _value.Length > 3)
                    _value = "\"" + _value.Substring(1, _value.Length - 2).Replace("''", "'") + "\"";
                return _value;
            }
            else if (_type == ExpressionType.NumberConstant)
            {
                return _value;
            }
            else if (_type == ExpressionType.Variable)
            {
                string value = null;
                Tag tag = null;

                object source = _source;
                while (!(source is Tag))
                {
                    source = ((Expression)source).Source;
                }

                Match match = Regex.Match(_value, @"(?<v>[a-zA-Z_]\w*)\[.*\]");
                if (match.Success)
                    tag = ((Tag)source).Engine.GetTagById(/*(Tag)source*/null, match.Groups["v"].Value);
                else
                    tag = (Tag)source;

                do
                {
                    if (tag == null)
                    {
                        value = ((Tag)source).Engine.ParseVariable(this);
                        break;
                    }

                    value = tag.ParseVariable(this);
                    if (value != null)
                        break;

                    tag = tag.Parent;
                }
                while (true);

                if (value == null)
                    return _value;
                else
                    return value;
            }
            else if (_type == ExpressionType.Member)
            {
                return _value;
            }
            else if (_type == ExpressionType.Function)
            {
                object source = _source;
                while (!(source is Tag))
                {
                    if (((Expression)source).Parent == null)
                        source = ((Expression)source).Source;
                    else
                        source = ((Expression)source).Parent.Source;
                }

                Tag tag = (Tag)source;
                string functionName = null;

                do
                {
                    if (tag == null)
                    {
                        functionName = ((Tag)source).Engine.ParseFunction(this);
                        break;
                    }

                    functionName = tag.ParseFunction(this);
                    if (functionName != null)
                        break;

                    tag = tag.Parent;
                }
                while (true);

                StringBuilder result = new StringBuilder();
                result.Append(functionName);
                result.Append("(");
                for (int i = 0; i < _params.Count; i++)
                {
                    if (i > 0)
                        result.Append(",");

                    result.Append(_params[i]);
                }
                result.Append(")");
                return result.ToString();
            }

            return String.Empty;
        }
        internal void InsertParam(int index, string param, bool isParsed)
        {
            string parsedParam = String.Empty;
            if (isParsed)
            {
                parsedParam = param;
            }
            else
            {
                Expression expression = new Expression(this, param);
                parsedParam = expression.Parse();
            }

            if (index == -1)
                _params.Add(parsedParam);
            else
                _params.Insert(index, parsedParam);
        }

        private void SliceToPart()
        {
            _value = _value.Trim();

            if (Regex.IsMatch(_value, @"^(-?\d+)(\.\d+)?$"))
            {
                _type = ExpressionType.NumberConstant;
                return;
            }

            int operatorIndex = -1;
            Stack<int> stack = new Stack<int>();
            for (int i = 0; i < _value.Length; i++)
            {
                if (_value[i] == ' ') continue;

                int stackIndex = (stack.Count > 0) ? stack.Peek() : -1;
                char stackChar = (stackIndex == -1) ? '\0' : _value[stackIndex];

                if (_value[i] == '\'')
                {
                    if (stackChar == '"')
                        continue;

                    if (stackChar == '\'')
                    {
                        if (i < _value.Length - 1 && _value[i + 1] == '\'')
                            continue;

                        stack.Pop();

                        if (i - stackIndex > 1)
                        {
                            _value = _value.Remove(stackIndex, 1);
                            _value = _value.Insert(stackIndex, "\"");

                            _value = _value.Remove(i, 1);
                            _value = _value.Insert(i, "\"");
                        }

                        if (stack.Count > 0)
                            continue;
                    }
                    else
                    {
                        stack.Push(i);
                    }
                }
                else if (_value[i] == '"')
                {
                    if (stackChar == '\'')
                        continue;

                    if (stackChar == '"')
                    {
                        if (i < _value.Length - 1 && _value[i + 1] == '"')
                            continue;

                        stack.Pop();

                        if (stack.Count > 0)
                            continue;
                    }
                    else
                    {
                        stack.Push(i);
                    }
                }
                else if (_value[i] == '(' || _value[i] == '[')
                {
                    if (stackChar == '\'' || stackChar == '"')
                        continue;

                    stack.Push(i);
                }
                else if (_value[i] == ')' || _value[i] == ']')
                {
                    if (stackChar == '\'' || stackChar == '"')
                        continue;

                    if ((_value[i] == ')' && stackChar != '(') || (_value[i] == ']' && stackChar != '['))
                    {
                        object source = _source;
                        while (!(source is Tag))
                        {
                            source = ((Expression)source).Source;
                        }

                        Exception exp = new Exception("lang_parse_template_expression_not_correct");
                        (source as Tag).Engine.ReportError(exp, (source as Tag).Index);
                    }

                    stack.Pop();

                    if (stack.Count > 0)
                        continue;
                }
                else if (_value[i] == '.' || _value[i] == '+' || _value[i] == '-' || _value[i] == '*' || _value[i] == '/' || _value[i] == '%' || _value[i] == '=' || _value[i] == '!' || _value[i] == '>' || _value[i] == '<' || _value[i] == '&' || _value[i] == '|')
                {
                    if (stackChar == '\'' || stackChar == '"')
                        continue;

                    if (stack.Count > 0)
                        continue;

                    string op = String.Empty;
                    if (i < _value.Length - 1 && _value[i] == '=' && _value[i + 1] == '=')
                        op = "==";
                    else if (i < _value.Length - 1 && _value[i] == '!' && _value[i + 1] == '=')
                        op = "!=";
                    else if (i < _value.Length - 1 && _value[i] == '>' && _value[i + 1] == '=')
                        op = ">=";
                    else if (i < _value.Length - 1 && _value[i] == '<' && _value[i + 1] == '=')
                        op = "<=";
                    else if (i < _value.Length - 1 && _value[i] == '&' && _value[i + 1] == '&')
                        op = "&&";
                    else if (i < _value.Length - 1 && _value[i] == '|' && _value[i + 1] == '|')
                        op = "||";
                    else
                        op = _value[i].ToString();

                    if (!_precedence.ContainsKey(op))
                        continue;

                    if (_operator == String.Empty)
                    {
                        operatorIndex = i;
                        _operator = op;
                    }
                    else if (_precedence[_operator] > _precedence[op])
                    {
                        operatorIndex = i;
                        _operator = op;
                    }

                    i += op.Length - 1;
                }
            }

            if (_operator == String.Empty)
            {
                if (_value.StartsWith("(") && _value.EndsWith(")"))
                {
                    _value = _value.Substring(1, _value.Length - 2);
                    SliceToPart();
                }
                else if ((_value.StartsWith("'") && _value.EndsWith("'")) || 
                        (_value.StartsWith("\"") && _value.EndsWith("\""))
                        )
                {
                    _type = ExpressionType.StringConstant;
                }
                else
                {
                    Match match = Regex.Match(_value, @"^(?<a>[a-zA-Z_]\w*)\((?<b>.*)\)$", RegexOptions.Compiled);
                    if (match.Success)
                    {
                        _type = ExpressionType.Function;
                        _functionName = match.Groups["a"].Value;
                        _params = new List<string>();
                        if (match.Groups["b"].Success)
                            SliceToParam(match.Groups["b"].Index, match.Groups["b"].Length);
                    }
                    else
                    {
                        if (_parent != null && _parent.Operator == "." && _parent.Right == this)
                            _type = ExpressionType.Member;
                        else
                            _type = ExpressionType.Variable;
                    }
                }
            }
            else
            {
                _left = new Expression(_source, _value.Substring(0, operatorIndex), this);
                _right = new Expression(_source, _value.Substring(operatorIndex + _operator.Length), this);
            }
        }
        private void SliceToParam(int index, int length)
        {
            Stack<char> stack = new Stack<char>();
            int end = index + length;
            for (int i = index; i < end; i++)
            {
                char c = _value[i];
                if (c == ' ') continue;

                char lastStackChar = (stack.Count > 0) ? stack.Peek() : '\0';

                if (c == '\'')
                {
                    if (lastStackChar == '\'')
                        stack.Pop();
                    else
                        stack.Push(c);
                }
                else if (lastStackChar != '\'')
                {
                    if (c == '(' || c == '[')
                    {
                        stack.Push(c);
                    }
                    else if (c == ')' || c == ']')
                    {
                        stack.Pop();
                    }
                    else if (c == ',')
                    {
                        if (stack.Count > 0)
                            continue;

                        InsertParam(-1, _value.Substring(index, i - index), false);
                        index = i + 1;
                    }
                }
            }

            if (length > 0)
            {
                InsertParam(-1, _value.Substring(index, end - index), false);
            }
        }

        #region Properies
        internal Object Source
        {
            get { return _source; }
        }
        internal string Value
        {
            get { return _value; }
        }
        internal ExpressionType Type
        {
            get { return _type; }
        }
        internal Expression Parent
        {
            get { return _parent; }
        }
        internal Expression Left
        {
            get { return _left; }
        }
        internal Expression Right
        {
            get { return _right; }
        }
        internal string Operator
        {
            get { return _operator; }
        }

        internal string FunctionName
        {
            get { return _functionName; }
            set { _functionName = value; }
        }
        internal List<string> Params
        {
            get { return _params; }
        }
        #endregion

        #region Fields
        private Object _source;
        private string _value;
        private ExpressionType _type;
        private Expression _parent;
        private Expression _left;
        private Expression _right;
        private string _operator;

        private string _functionName;
        private List<string> _params;//当_type为Function（函数）时用。


        private static Dictionary<string, int> _precedence;
        #endregion
    }
}
